﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace WpfApp1
{
    internal class Program
    {
        public static void MainMethod()
        {
            BindingFlags bindingFlags = BindingFlags.Static | BindingFlags.Public;
            //var types = new Type[] { typeof(string) };
            //types = null;
            //var parameterModifier = new ParameterModifier[] { new ParameterModifier(1) };
            //parameterModifier = null;
            //var oldMethod = typeof(TestClass).GetMethod(nameof(TestClass.WriteLine),
            //    bindingFlags,
            //    null,
            //    CallingConventions.Standard,
            //    types,
            //    parameterModifier);
            //var newMethod = typeof(Program).GetMethod(nameof(Program.WriteLine),
            //    bindingFlags,
            //    null,
            //    CallingConventions.Standard,
            //    types,
            //    parameterModifier);

            //ReplaceMethod(oldMethod, newMethod);

            TestClass.WriteLine();
            WriteLine();

            var oldMethod = typeof(TestClass).GetMethod(nameof(WriteLine), bindingFlags);
            var newMethod = typeof(Program).GetMethod(nameof(WriteLine), bindingFlags);
            ReplaceMethod(oldMethod, newMethod);

            TestClass.WriteLine();
            WriteLine();
        }

        public static void WriteLine()
        {
            Console.WriteLine("Program");
        }

        public static void ReplaceMethod(MethodInfo methodToReplace, MethodInfo methodToInject)
        {
            RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle);
            RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle);

            unsafe
            {
                if (IntPtr.Size == 4)
                {
                    int* inj = (int*)methodToInject.MethodHandle.Value.ToPointer() + 2;
                    int* tar = (int*)methodToReplace.MethodHandle.Value.ToPointer() + 2;
#if DEBUG
                    byte* injInst = (byte*)*inj;
                    byte* tarInst = (byte*)*tar;

                    int* injSrc = (int*)(injInst + 1);
                    int* tarSrc = (int*)(tarInst + 1);

                    *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5);
#endif
                    *tar = *inj;
                }
                else
                {

                    long* inj = (long*)methodToInject.MethodHandle.Value.ToPointer() + 1;
                    long* tar = (long*)methodToReplace.MethodHandle.Value.ToPointer() + 1;
#if DEBUG
                    byte* injInst = (byte*)inj;
                    byte* tarInst = (byte*)tar;

                    int* injSrc = (int*)(injInst + 1);
                    int* tarSrc = (int*)(tarInst + 1);

                    *tar = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5);
#endif

                    *tar = *inj;
                }
            }
        }


        public static bool ReplaceMethod(Type targetType, string targetMethod, Type injectType, string injectMethod, BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, Binder binder = null, CallingConventions callConvention = CallingConventions.Any, Type[] types = null, ParameterModifier[] modifiers = null)
        {
            if (types == null)
            {
                types = Type.EmptyTypes;
            }
            MethodInfo tarMethod = targetType.GetMethod(targetMethod, bindingAttr, binder, callConvention, types, modifiers);
            MethodInfo injMethod = injectType.GetMethod(injectMethod, bindingAttr, binder, callConvention, types, modifiers);
            if (tarMethod == null || injMethod == null)
            {
                return false;
            }
            RuntimeHelpers.PrepareMethod(tarMethod.MethodHandle);
            RuntimeHelpers.PrepareMethod(injMethod.MethodHandle);
            unsafe
            {
                if (IntPtr.Size == 4)
                {
                    int* tar = (int*)tarMethod.MethodHandle.Value.ToPointer() + 2;
                    int* inj = (int*)injMethod.MethodHandle.Value.ToPointer() + 2;
                    *tar = *inj;
                }
                else
                {
                    long* tar = (long*)tarMethod.MethodHandle.Value.ToPointer() + 1;
                    long* inj = (long*)injMethod.MethodHandle.Value.ToPointer() + 1;
                    *tar = *inj;
                }
            }
            return true;
        }
        private static void FooStatic()
        {
            Console.WriteLine("FooStatic");
        }
    }

    class Class1
    {
        public void Demo()
        {
            Console.WriteLine("Demo");
        }

        public void Demo(int d)
        {
            Console.WriteLine("Demo: {0}", d);
        }

        public static void DemoStatic()
        {
            Console.WriteLine("DemoStatic");
        }
    }

    public class TestClass
    {
        public static void WriteLine() => Console.WriteLine("TestClass");
    }
}
